﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows.Media;
using Microsoft.Practices.CompositeUI.EventBroker;
using SCSFContrib.CompositeUI.WPF.Commands;
using WPFCABShell.Shell.Interface.Diagnostics;
using WPFCABShell.Shell.Interface.Services.Command;
using Kent.Boogaart.HelperTrinity;
using CAB = Microsoft.Practices.CompositeUI.Commands;
using WPF = System.Windows.Input;

namespace WPFCABShell.Shell.Services.Command
{
	public sealed class CommandService : ICommandService
	{
		private readonly ObservableCollection<ICommand> _registeredCommands;
		private readonly ReadOnlyObservableCollection<ICommand> _exposedRegisteredCommands;
		private static readonly Log _log = Log.CreateForType(typeof(CommandService));

		public IList<ICommand> RegisteredCommands
		{
			get
			{
				return _exposedRegisteredCommands;
			}
		}

		[EventPublication(Events.CommandRegistered)]
		public event EventHandler<CommandEventArgs> CommandRegistered;

		[EventPublication(Events.CommandExecuted)]
		public event EventHandler<CommandEventArgs> CommandExecuted;

		[EventPublication(Events.CommandUnregistered)]
		public event EventHandler<CommandEventArgs> CommandUnregistered;

		public CommandService()
		{
			_registeredCommands = new ObservableCollection<ICommand>();
			_exposedRegisteredCommands = new ReadOnlyObservableCollection<ICommand>(_registeredCommands);
		}

		public ICommand CreateCommand(string name, ImageSource image)
		{
			ArgumentHelper.AssertNotNull(name, "name");
			NullCommand command = new NullCommand(Guid.NewGuid().ToString(), name, image);
			_log.Verbose("Created command with ID '{0}' named '{1}'.", command.Id, name);
			return command;
		}

		public ICommand CreateCommand(CAB.Command command, string name, ImageSource image)
		{
			ArgumentHelper.AssertNotNull(command, "command");
			ArgumentHelper.AssertNotNull(name, "name");
			ICommand commandWrapper = new CabCommand(command, Guid.NewGuid().ToString(), null, name, image);
			AttachTo(commandWrapper);
			_log.Verbose("Created command with ID '{0}' named '{1}' which wraps CAB command named '{2}'.", commandWrapper.Id, name, command.Name);
			return commandWrapper;
		}

		public ICommand CreateCommand(WPF.ICommand command, string name, ImageSource image)
		{
			ArgumentHelper.AssertNotNull(command, "command");

			if ((name == null) && (command is WPF.RoutedUICommand))
			{
				name = (command as WPF.RoutedUICommand).Text;
			}

			ICommand commandWrapper = new WpfCommand(command, Guid.NewGuid().ToString(), null, name, image);
			AttachTo(commandWrapper);
			_log.Verbose("Created command with ID '{0}' named '{1}' which wraps WPF command.", commandWrapper.Id, name);
			return commandWrapper;
		}

		public ICommand CreateAndRegisterCommand(CAB.Command command, string id, string category, string name, ImageSource image)
		{
			ArgumentHelper.AssertNotNull(command, "command");
			ArgumentHelper.AssertNotNull(id, "id");
			ArgumentHelper.AssertNotNull(category, "category");
			ArgumentHelper.AssertNotNull(name, "name");

			ICommand existingCommand = FindRegisteredCommand(id);
			ExceptionHelper.ThrowIf(existingCommand != null, "CommandAlreadyRegistered", id);

			ICommand commandWrapper = new CabCommand(command, id, category, name, image);
			AttachTo(commandWrapper);
			_registeredCommands.Add(commandWrapper);
			OnCommandRegistered(commandWrapper);
			_log.Verbose("Created and registered command with ID '{0}' named '{1}' which wraps CAB command named '{2}'.", commandWrapper.Id, name, command.Name);
			return commandWrapper;
		}

		public ICommand CreateAndRegisterCommand(WPF.ICommand command, string id, string category, string name, ImageSource image)
		{
			ArgumentHelper.AssertNotNull(command, "command");
			ArgumentHelper.AssertNotNull(id, "id");
			ArgumentHelper.AssertNotNull(category, "category");

			ICommand existingCommand = FindRegisteredCommand(id);
			ExceptionHelper.ThrowIf(existingCommand != null, "CommandAlreadyRegistered", id);

			if ((name == null) && (command is WPF.RoutedUICommand))
			{
				name = (command as WPF.RoutedUICommand).Text;
			}

			WpfCommand commandWrapper = new WpfCommand(command, id, category, name, image);
			AttachTo(commandWrapper);
			_registeredCommands.Add(commandWrapper);
			OnCommandRegistered(commandWrapper);
			_log.Verbose("Created and registered command with ID '{0}' named '{1}' which wraps WPF command.", commandWrapper.Id, name);
			return commandWrapper;
		}

		public void UnregisterCommand(ICommand command)
		{
			ArgumentHelper.AssertNotNull(command, "command");

			if (_registeredCommands.Remove(command))
			{
				DetachFrom(command);
				OnCommandUnregistered(command);
				_log.Verbose("Unregistered command with ID '{0}' named '{1}'.", command.Id, command.Name);
			}
		}

		private ICommand FindRegisteredCommand(string id)
		{
			Debug.Assert(id != null);

			foreach (ICommand command in RegisteredCommands)
			{
				if (string.Equals(id, command.Id, StringComparison.Ordinal))
				{
					return command;
				}
			}

			return null;
		}

		private void AttachTo(ICommand command)
		{
			command.Executed += command_Executed;
		}

		private void DetachFrom(ICommand command)
		{
			command.Executed -= command_Executed;
		}

		private void command_Executed(object sender, EventArgs e)
		{
			OnCommandExecuted(sender as ICommand);
		}

		private void OnCommandRegistered(ICommand command)
		{
			EventHelper.Raise(CommandRegistered, this, new CommandEventArgs(command));
		}

		private void OnCommandExecuted(ICommand command)
		{
			EventHelper.Raise(CommandExecuted, this, new CommandEventArgs(command));
		}

		private void OnCommandUnregistered(ICommand command)
		{
			EventHelper.Raise(CommandUnregistered, this, new CommandEventArgs(command));
		}

		#region Command Classes

		private abstract class CommandBase : ICommand, INotifyPropertyChanged, IEquatable<CommandBase>
		{
			private readonly string _id;
			private readonly string _category;
			private readonly string _name;
			private ImageSource _image;

			public string Id
			{
				get
				{
					return _id;
				}
			}

			public string Category
			{
				get
				{
					return _category;
				}
			}

			public string Name
			{
				get
				{
					return _name;
				}
			}

			public string Text
			{
				get
				{
					return Name.Replace("_", string.Empty);
				}
			}

			public ImageSource Image
			{
				get
				{
					return _image;
				}
				set
				{
					if (_image != value)
					{
						_image = value;
						OnPropertyChanged("Image");
					}
				}
			}

			public virtual bool IsNullCommand
			{
				get
				{
					return false;
				}
			}

			public event PropertyChangedEventHandler PropertyChanged;

			public event EventHandler<EventArgs> Executed;

			public abstract event EventHandler CanExecuteChanged;

			protected CommandBase(string id, string category, string name, ImageSource image)
			{
				Debug.Assert(id != null);

				_id = id;
				_category = category;
				_name = name;
				_image = image;
			}

			public abstract bool CanExecute(object parameter);

			public void Execute(object parameter)
			{
				OnExecuted();
				ExecuteCore(parameter);
				_log.Verbose("Executed command with ID '{0}'. Parameter: {1}", Id, parameter);
			}

			protected abstract void ExecuteCore(object parameter);

			private void OnExecuted()
			{
				EventHelper.Raise(Executed, this, EventArgs.Empty);
			}

			protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
			{
				EventHelper.Raise(PropertyChanged, this, e);
			}

			protected void OnPropertyChanged(string propertyName)
			{
				OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
			}

			public bool Equals(CommandBase other)
			{
				if (other == null)
				{
					return false;
				}
				
				if (object.ReferenceEquals(this, other))
				{
					return true;
				}

				return string.Equals(_id, other._id, StringComparison.Ordinal);
			}

			public override bool Equals(object obj)
			{
				return Equals(obj as CommandBase);
			}

			public override int GetHashCode()
			{
				return _id.GetHashCode();
			}
		}

		private sealed class NullCommand : CommandBase
		{
			public override bool IsNullCommand
			{
				get
				{
					return true;
				}
			}

			public override event EventHandler CanExecuteChanged
			{
				add
				{
					//nop
				}
				remove
				{
					//nop
				}
			}

			public NullCommand(string id, string name, ImageSource image)
				: base(id, null, name, image)
			{
			}

			public override bool CanExecute(object parameter)
			{
				return true;
			}

			protected override void ExecuteCore(object parameter)
			{
				//nop
			}
		}

		private sealed class CabCommand : CommandBase
		{
			private readonly CAB.Command _cabCommand;

			public override event EventHandler CanExecuteChanged;

			public CabCommand(CAB.Command cabCommand, string id, string category, string name, ImageSource image)
				: base(id, category, name, image)
			{
				Debug.Assert(cabCommand != null);

				_cabCommand = cabCommand;
				_cabCommand.Changed += delegate
				{
					OnCanExecuteChanged();
				};
			}

			public override bool CanExecute(object parameter)
			{
				return _cabCommand.Status == Microsoft.Practices.CompositeUI.Commands.CommandStatus.Enabled;
			}

			protected override void ExecuteCore(object parameter)
			{
				_cabCommand.Execute();
			}

			private void OnCanExecuteChanged()
			{
				EventHelper.Raise(CanExecuteChanged, this, EventArgs.Empty);
			}
		}

		private sealed class WpfCommand : CommandBase
		{
			private readonly WPF.ICommand _wpfCommand;

			public override event EventHandler CanExecuteChanged
			{
				add
				{
					_wpfCommand.CanExecuteChanged += value;
				}
				remove
				{
					_wpfCommand.CanExecuteChanged -= value;
				}
			}

			public WpfCommand(WPF.ICommand wpfCommand, string id, string category, string name, ImageSource image)
				: base(id, category, name, image)
			{
				Debug.Assert(wpfCommand != null);
				_wpfCommand = wpfCommand;
			}

			public override bool CanExecute(object parameter)
			{
				return _wpfCommand.CanExecute(parameter);
			}

			protected override void ExecuteCore(object parameter)
			{
				_wpfCommand.Execute(parameter);
			}
		}

		#endregion
	}
}
